import { promises as fs } from 'fs'
import * as path from 'path'
import type { HistoryVersion, VersionEntries, FileHistoryRecord } from '../types/basic'
import { ErrorType, type AppError } from '../types/errors'

export class HistoryParserImpl {
  private historyDir: string
  private entriesCache = new Map<string, VersionEntries>()
  private versionsCache: HistoryVersion[] | null = null
  private contentCache = new Map<string, string>()

  constructor(historyDir: string = 'History') {
    this.historyDir = path.resolve(historyDir)
  }

  async scanHistoryVersions(): Promise<HistoryVersion[]> {
    // Return cached versions if available
    if (this.versionsCache) {
      return this.versionsCache
    }

    try {
      // Check if History directory exists
      await fs.stat(this.historyDir)
    } catch (error) {
      throw this.createError(
        ErrorType.FILE_NOT_FOUND,
        `History directory not found: ${this.historyDir}`,
        error
      )
    }

    try {
      const entries = await fs.readdir(this.historyDir, { withFileTypes: true })
      const versions: HistoryVersion[] = []

      for (const entry of entries) {
        if (!entry.isDirectory()) continue

        // Validate version directory name format (should start with - or be alphanumeric)
        const versionId = entry.name
        if (!this.isValidVersionId(versionId)) continue

        try {
          const versionPath = path.join(this.historyDir, versionId)
          const stats = await fs.stat(versionPath)
          
          // Check if entries.json exists
          const entriesPath = path.join(versionPath, 'entries.json')
          let hasEntriesFile = false
          try {
            await fs.access(entriesPath)
            hasEntriesFile = true
          } catch {
            // entries.json doesn't exist
          }

          // Count files (excluding entries.json)
          const versionFiles = await fs.readdir(versionPath)
          const fileCount = versionFiles.filter(file => file !== 'entries.json').length

          versions.push({
            id: versionId,
            timestamp: stats.mtime.getTime(),
            fileCount,
            hasEntriesFile
          })
        } catch (error) {
          // Skip this version if we can't read it
          console.warn(`Failed to process version ${versionId}:`, error)
          continue
        }
      }

      // Sort by timestamp (newest first)
      versions.sort((a, b) => b.timestamp - a.timestamp)
      
      // Cache the results
      this.versionsCache = versions
      return versions
    } catch (error) {
      throw this.createError(
        ErrorType.FILE_NOT_FOUND,
        `Failed to scan history directory: ${this.historyDir}`,
        error
      )
    }
  }

  async parseVersionEntries(versionId: string): Promise<VersionEntries> {
    // Return cached entries if available
    if (this.entriesCache.has(versionId)) {
      return this.entriesCache.get(versionId)!
    }

    const entriesPath = path.join(this.historyDir, versionId, 'entries.json')

    try {
      await fs.access(entriesPath)
    } catch (error) {
      throw this.createError(
        ErrorType.FILE_NOT_FOUND,
        `entries.json not found for version ${versionId}`,
        error
      )
    }

    try {
      const content = await fs.readFile(entriesPath, 'utf-8')
      const data = JSON.parse(content)

      // Validate the structure
      if (!this.isValidEntriesData(data)) {
        throw this.createError(
          ErrorType.VALIDATION_ERROR,
          `Invalid entries.json structure for version ${versionId}`,
          data
        )
      }

      // Normalize entries data
      const entries: VersionEntries = {
        version: data.version,
        resource: this.decodeResourcePath(data.resource),
        entries: data.entries.map((entry: any) => ({
          id: entry.id,
          source: entry.source || undefined,
          timestamp: entry.timestamp
        }))
      }

      // Cache the results
      this.entriesCache.set(versionId, entries)
      return entries
    } catch (error) {
      if (error instanceof SyntaxError) {
        throw this.createError(
          ErrorType.PARSING_ERROR,
          `Invalid JSON format in entries.json for version ${versionId}`,
          error
        )
      }
      throw error
    }
  }

  async getFileHistory(filePath: string): Promise<FileHistoryRecord[]> {
    const versions = await this.scanHistoryVersions()
    const history: FileHistoryRecord[] = []

    for (const version of versions) {
      if (!version.hasEntriesFile) continue

      try {
        const entries = await this.parseVersionEntries(version.id)
        
        // Check if this version contains the target file
        if (this.normalizeFilePath(entries.resource) === this.normalizeFilePath(filePath)) {
          for (const entry of entries.entries) {
            const filePath = path.join(this.historyDir, version.id, entry.id)
            let size = 0
            let exists = true

            try {
              const stats = await fs.stat(filePath)
              size = stats.size
            } catch {
              exists = false
            }

            history.push({
              versionId: version.id,
              fileId: entry.id,
              originalPath: entries.resource,
              timestamp: entry.timestamp,
              source: entry.source,
              size,
              exists
            })
          }
        }
      } catch (error) {
        // Skip this version if we can't parse it
        console.warn(`Failed to parse entries for version ${version.id}:`, error)
        continue
      }
    }

    // Sort by timestamp (newest first)
    history.sort((a, b) => b.timestamp - a.timestamp)
    return history
  }

  async getFileContent(versionId: string, fileId: string): Promise<string> {
    const cacheKey = `${versionId}:${fileId}`
    
    // Return cached content if available
    if (this.contentCache.has(cacheKey)) {
      return this.contentCache.get(cacheKey)!
    }

    const filePath = path.join(this.historyDir, versionId, fileId)

    try {
      await fs.access(filePath)
    } catch (error) {
      throw this.createError(
        ErrorType.FILE_NOT_FOUND,
        `File ${fileId} not found in version ${versionId}`,
        error
      )
    }

    try {
      const content = await fs.readFile(filePath, 'utf-8')
      
      // Cache the content (with size limit to prevent memory issues)
      if (content.length < 1024 * 1024) { // Cache files smaller than 1MB
        this.contentCache.set(cacheKey, content)
      }
      
      return content
    } catch (error) {
      throw this.createError(
        ErrorType.FILE_NOT_FOUND,
        `Failed to read file ${fileId} from version ${versionId}`,
        error
      )
    }
  }

  // Clear all caches
  clearCache(): void {
    this.entriesCache.clear()
    this.contentCache.clear()
    this.versionsCache = null
  }

  // Get cache statistics
  getCacheStats() {
    return {
      entriesCount: this.entriesCache.size,
      contentCount: this.contentCache.size,
      hasVersionsCache: this.versionsCache !== null
    }
  }

  // Get all files in a specific version
  async getVersionFiles(versionId: string): Promise<string[]> {
    const versionPath = path.join(this.historyDir, versionId)
    
    try {
      const files = await fs.readdir(versionPath)
      return files.filter(file => file !== 'entries.json')
    } catch (error) {
      throw this.createError(
        ErrorType.FILE_NOT_FOUND,
        `Failed to read version directory: ${versionId}`,
        error
      )
    }
  }

  // Check if a specific version exists
  async versionExists(versionId: string): Promise<boolean> {
    const versionPath = path.join(this.historyDir, versionId)
    
    try {
      const stats = await fs.stat(versionPath)
      return stats.isDirectory()
    } catch {
      return false
    }
  }

  // Get file statistics for a specific file in a version
  async getFileStats(versionId: string, fileId: string) {
    const filePath = path.join(this.historyDir, versionId, fileId)
    
    try {
      const stats = await fs.stat(filePath)
      return {
        size: stats.size,
        modifiedTime: stats.mtime,
        isDirectory: stats.isDirectory(),
        exists: true
      }
    } catch (error) {
      return {
        size: 0,
        modifiedTime: new Date(0),
        isDirectory: false,
        exists: false
      }
    }
  }

  // Search for files by name pattern across all versions
  async searchFilesByName(pattern: string): Promise<Array<{
    versionId: string
    fileId: string
    originalPath?: string
    timestamp: number
  }>> {
    const versions = await this.scanHistoryVersions()
    const results: Array<{
      versionId: string
      fileId: string
      originalPath?: string
      timestamp: number
    }> = []

    const regex = new RegExp(pattern, 'i')

    for (const version of versions) {
      try {
        const files = await this.getVersionFiles(version.id)
        
        for (const file of files) {
          if (regex.test(file)) {
            let originalPath: string | undefined
            let timestamp = version.timestamp

            // Try to get more info from entries.json if available
            if (version.hasEntriesFile) {
              try {
                const entries = await this.parseVersionEntries(version.id)
                const entry = entries.entries.find(e => e.id === file)
                if (entry) {
                  originalPath = entries.resource
                  timestamp = entry.timestamp
                }
              } catch {
                // Ignore parsing errors
              }
            }

            results.push({
              versionId: version.id,
              fileId: file,
              originalPath,
              timestamp
            })
          }
        }
      } catch {
        // Skip versions that can't be read
        continue
      }
    }

    // Sort by timestamp (newest first)
    results.sort((a, b) => b.timestamp - a.timestamp)
    return results
  }

  private isValidVersionId(versionId: string): boolean {
    // Version IDs should start with - or be alphanumeric
    return /^[-a-zA-Z0-9]+$/.test(versionId)
  }

  private isValidEntriesData(data: any): boolean {
    if (!data || typeof data !== 'object') return false
    
    if (typeof data.version !== 'number') {
      throw this.createError(
        ErrorType.VALIDATION_ERROR,
        'Invalid version number in entries.json',
        data.version
      )
    }
    
    if (typeof data.resource !== 'string') return false
    if (!Array.isArray(data.entries)) return false
    
    // Validate each entry
    for (const entry of data.entries) {
      if (!entry || typeof entry !== 'object') return false
      if (typeof entry.id !== 'string') return false
      if (typeof entry.timestamp !== 'number') return false
      // source is optional
    }
    
    return true
  }

  private decodeResourcePath(resource: string): string {
    try {
      // Handle file:// URLs
      if (resource.startsWith('file://')) {
        const url = new URL(resource)
        return decodeURIComponent(url.pathname)
      }
      return resource
    } catch {
      return resource
    }
  }

  private normalizeFilePath(filePath: string): string {
    // Normalize path separators and remove drive letters for comparison
    return filePath
      .replace(/\\/g, '/')
      .replace(/^[a-zA-Z]:/, '')
      .toLowerCase()
  }

  private createError(type: ErrorType, message: string, details?: any): AppError {
    return {
      type,
      message,
      details,
      timestamp: new Date(),
      recoverable: type !== ErrorType.INVALID_HISTORY_FORMAT
    }
  }
}

// Create default instance
export const historyParser = new HistoryParserImpl('History')